﻿namespace Blaze.FX
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Framework;
    using Framework.Logging;
    using UnityEngine;

    /// <summary>
    /// 屏幕模糊静态效果。
    /// </summary>
    public class ScreenBlurStaticEffect : MonoBehaviour
    {
        public Material Material;

        [Range(0, 20)]
        public int MaxStrength = 20;

        [Range(0.1f, 1)]
        public float SampleLevel = 0.125f;

        public float Strength;

        /// <summary>
        /// 获取一个值，表示是否应用特效。
        /// </summary>
        protected bool ShouldApplyEffect
        {
            get { return mDisableAtFrame != Time.frameCount || !mIsInitialized; }
        }

        protected Texture2D Texture { get; private set; }

        protected virtual void OnDisable()
        {
            mDisableAtFrame = Time.frameCount;
            if (mIsQuit)
                return;
            if (mCoroutine == null)
                return;
            CoroutineManager.Stop(mCoroutine);
        }

        protected virtual void OnEnable()
        {
            if (ShouldApplyEffect)
            {
                mMeshRenderer.enabled = false;
                Strength = 0;
            }
            mCoroutine = CoroutineManager.StartNew(applyEffect());
        }

        protected virtual void OnPostProcess(Color32[] pixels)
        {
        }

        private static int sortByDepth(Camera a, Camera b)
        {
            if (a.depth - b.depth > 0)
                return 1;
            if (a.depth - b.depth < 0)
                return -1;
            return 0;
        }

        private IEnumerator applyEffect()
        {
            if (ShouldApplyEffect)
            {
                yield return YielderFactory.WaitForEndOfFrame();
                initializeEffect();
            }

            while (true)
            {
                if (mLastStrength == Strength)
                {
                    yield return null;
                    continue;
                }
                Array.Copy(mPixels, mModifiedPixels, mPixels.Length);

                mStackBlur.Apply(mModifiedPixels, (int) Strength);
                mMeshRenderer.enabled = true;
                OnPostProcess(mModifiedPixels);
                Texture.SetPixels32(mModifiedPixels);
                Texture.Apply();
                mLastStrength = Strength;
                yield return null;
            }
        }

        private void Awake()
        {
            mMeshRenderer = GetComponent<MeshRenderer>();
        }

        private void initializeEffect()
        {
            var width = (int) (Screen.width*SampleLevel);
            var height = (int) (Screen.height*SampleLevel);

            if (Texture == null)
            {
                Texture = new Texture2D(width, height, TextureFormat.RGB24, false);
                mStackBlur = new StackBlur(Texture.width, Texture.height, MaxStrength);
            }

            mCameras.Clear();
            mCameras.AddRange(Camera.allCameras);
            mCameras.Sort(sortByDepth);

            if (mRenderTexture == null)
                mRenderTexture = RenderTexture.GetTemporary(width, height);
            RenderTexture.active = mRenderTexture;

            for (var i = 0; i < mCameras.Count; i++)
            {
                var c = mCameras[i];
                c.targetTexture = mRenderTexture;
                c.Render();
                c.targetTexture = null;
            }

            Texture.ReadPixels(new Rect(0, 0, width, height), 0, 0, false);
            Texture.Apply();

            RenderTexture.active = null;

            Material.mainTexture = Texture;
            mPixels = Texture.GetPixels32();

            if (mModifiedPixels == null)
                mModifiedPixels = new Color32[mPixels.Length];
            mIsInitialized = true;
        }

        private void OnApplicationQuit()
        {
            mIsQuit = true;
        }

        private void OnDestroy()
        {
            RenderTexture.ReleaseTemporary(mRenderTexture);
        }

        private static Log mLog = LogManager.GetLogger(typeof (ScreenBlurStaticEffect));
        private readonly List<Camera> mCameras = new List<Camera>();
        private Coroutine mCoroutine;
        private int mDisableAtFrame = -1;
        private bool mIsInitialized;
        private bool mIsQuit;
        private float mLastStrength = -1;
        private MeshRenderer mMeshRenderer;
        private Color32[] mModifiedPixels;
        private Color32[] mPixels;
        private RenderTexture mRenderTexture;
        private StackBlur mStackBlur;
    }
}